/*
    物理内存分配器。以页为单位分配
*/
#include "riscv.h"
#include "memlayout.h" 
#include "common.h"
#include "spinlock.h"
// 在链接脚本中定义，指向内核结束位置，即第一个可用地址
// C语言中可以直接使用数组名表示地址，声明end是一个外部符号，链接器中设置其地址值
extern char end[];
// 空闲页面链表节点
struct run {
    struct run *next;
};

// 空闲页面链表结构
struct {
    struct spinlock lock;
    struct run *freelist;
} kmem;

// 释放pa开始的单页
void kfree(void *pa) {
    struct run *r;      // 待释放的节点
    // 检查地址是否对齐以及是否在合法范围内
    if((uint64)pa % PGSIZE != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP) {
        panic("kfree");
        return;
    }
    memset(pa, 1, PGSIZE);    // 向pa开始的页填充垃圾数据，便于区分
    r = (struct run*)pa;      // 将pa转换为空闲页面节点
    acquire(&kmem.lock);      // 加锁
    r->next = kmem.freelist;  // 采用头插法插入空闲链表
    kmem.freelist = r;
    release(&kmem.lock);      // 解锁
}

// 释放从pa_start到pa_end的物理内存
void freerange(void *pa_start, void *pa_end) {
    char *p;
    p = (char*)PGROUNDUP((uint64)pa_start);  // 将起始地址按页向上对齐
    // 遍历逐页释放
    for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE) {
        kfree(p);
    }
}

// 分配1页物理内存，返回起始地址，返回0表示分配失败
void* kalloc(void) {
    struct run *r;
    acquire(&kmem.lock);          // 加锁
    r = kmem.freelist;           // 采用头删法分配
    if(r) {
        kmem.freelist = r->next;
        memset(r, 5, PGSIZE);    // 给分配的页填充垃圾值
    }
    release(&kmem.lock);          // 解锁
    return (void*)r;
}

// 初始化内存分配器
void kinit() {
    initlock(&kmem.lock, "kmem");  // 初始化自旋锁
    freerange(end, (void*)PHYSTOP);  // 释放从end到PHYSTOP的物理内存（即将其加入空闲链表管理）
    printf("物理内存分配器初始化完成！\n");
}
